package com.briup.day17;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Properties;
import java.util.TreeMap;

public class MapTest {
    public static void main(String[] args) throws NoSuchFieldException {
        /*
         创建HashMap对象，如果使用的是无参构造器，那么会设置一个loadFactor加载因子，
         LoadFactor的作用在于设定扩容的界限，默认为0.75f；集合中底层的数组长度乘以加载因子
         的结果就是数组扩容的界限，如果数组长度为16，那么界限就是12，即当HashMap集合中的元素个数
         达到12个时，会对数组进行扩容，每一次扩容都是原本数组长度的2倍，每一次扩容都会对集合中的元素进行
         重新计算，重新存放，所以HashMap中的元素是无序；
         如果在创建HashMap对象时，没有指定数组的初始容量，那么会在第一次添加元素时，
         给数组设置长度为默认的16；如果你在创建HashMap时，指定了数组的初始容量，那么
         HashMap数组的初始容量为离你指定的容量最近的2的n次方，如果本身就是2的n次方那么会指定它为初始容量
         */
        HashMap<String, String> map = new HashMap<>();
        /*
            在执行put()方法时，内部调用了一个putVal()方法，在调用putVal()方法时
            传参中调用了hash(key)方法对你要添加的元素key计算其对应的hash值，
            然后在putVal()方法中，会先确定你是否为第一次添加元素或是否给数组进行过初始化，
            如果没有进行过初始化，那么会先初始化，如设定长度为默认的16；
            然后根据key的hash值与数组长度进行与运算，计算出该ke应该存放在数组中哪一个下标位置，
            然后获取该下标位置中存储的元素，如果为null，那么直接将新添加的key和value放入到一个新
            创建的Node对象中，然后将Node对象放入到该下标位置中；
            如果是后续添加元素，找到对应下标位置后，如果发现该位置中存在对应的元素，那么会将新加的
            key与该元素的key进行比较，放置出现重复的key对象，比较的重点为hash值、地址值以及通过equals方法
            比较对象内部的内容，如果这一整条链表中都不存在重复的元素，那么能将待加入的数据添加到集合中
         */
        map.put("abc", "123");
        System.out.println(map);
    }

    private static void test2() {
        /*
            Map集合存储的是能够表现出key和value之间的映射关系的双列集合，
            即可以通过输入key从而找到其对应的value；
            在Map集合中，其最顶层的接口为Map接口，在Map接口中定义了所有的Map集合
            都需要定义的方法，具体如下：
                V put(K k,V v)：将key和value值放入到集合中，在放入的过程中，
                会对Key进行校验比较，如果在集合中存在相同的key，那么就会让新传入的
                value值替代原本存在的value；
                V get(K k)：根据你传入的key在集合中找到其对应的value值，如key存在
                那么返回实际的value，如果key不存在则返回null；
                boolean containsKey(K k)：根据你传入的key在集合中查找该key是否存在，
                如存在返回true，如果不存在则返回false；
                V putIfAbsent(K k,V v)：根据你传入的key和value，如果key在集合中存在，
                那么直接返回对应的Value值，如果key不存在那么直接将key和value放入集合中
                V getOrDefault(Object key, V defaultValue)：根据你传入的key和默认的结果值，
                在集合中查找，如果key不存在或key对应的value值为null，那么返回你指定的默认value值，
                如果存在则返回实际的value值
         Map集合中常用的实现类：
            HashMap:
                底层使用的是数组+单向链表+红黑树的结构，其存取数据的速度非常快（不需要比较所有的元素），
                在每次存储数据时，都会先根据key计算其对应的hash值，然后根据hash值进行取模计算，
                算出该key应该存储在数组中的某一个下标位置，然后该根据equals()方法和hashCode()方法
                比较该key与数组中该下标位置的元素内容是否相同；key值只能存在一个null，value无所谓是否为null
            TreeMap:
                底层使用的是红黑树结构，因为红黑树是一种平衡二叉树，能够根据你传入的元素值，
                动态的调整树结构，使该树型结构始终保持相对平衡，如此就能避免存储元素时
                出现单列左子树或右子树的问题；TreeMap能够根据你传入的排序逻辑对Map集合中的key元素
                进行排序，如此在读取数据时，得到的就是排序后的结果；在读取树中元素时使用的是中序遍历，
                即左中右的顺序；
                在TreeMap中存储的key能否为null?
                    不能，因为在TreeMap中存储的key值，每一次添加元素时都需要调用compare()方法，
                    或调用其本身的compareTo()方法与其他已经存在元素进行比较，如果key为null，那么就会
                    报NullPointerException空指针异常
            Properties:
                是Hashtable类下的子类，是线程安全的，同时在Properties集合的key和value中
                都不能存储null值；
                在文件格式中存在一个.properties格式，天然支持key=value形式的内容格式，
                和Map集合格式一致，使用Properties集合能够直接将.properties文件中的数据加载读取
                到程序的集合中，而不需要我们进行复杂的拆解
         */
        // HashMap
        HashMap<String, String> map = new HashMap<>();
        /* 使用put方法将带有映射关系的key和value存入到集合中，在存入集合内部之后，
         HashMap会自动将key和value维护在一个Node节点中，在该Node节点中至少维护了
         Key、Value和对Key进行计算后的系数hash（用于后续在红黑树中进行比较）*/
        map.put("hello", "world");
        map.put(null, null);
        // 第二次添加null时，不会新加一个Node节点，而是找到原本存在的Node节点，
        // 将其内部的Value替换为新传入的value值；原本的value值会随着返回值进行返回
        String abc = map.put(null, "abc");
        System.out.println(abc);
        System.out.println(map);
        /*
         修改Map集合中对应key值的value值方式：
            1、put()方法
            2、replace(K k,V v)方法：直接找到key所在的node节点，
            然后更新该Node对象内部的value属性；
            3、replace(K k,V oldV,V newV)方法：根据Key找到其所在的node节点，
            然后在比较你传入的ov与原本存储的value，如相同那么可以修改value为nv，
            如果比较不通过，那么修改也不通过
         */
        map.replace(null, "哈喽");
        map.replace("hello", "world", "世界");
        System.out.println(map);

        // TreeMap
        TreeMap<String, String> map2 = new TreeMap<>();
        // 无法将为null的数据存入到TreeMap集合中，会报空指针异常
//        map2.put(null, null);
        map2.put("a", "b");
        System.out.println(map2);

        // Properties
        try (FileInputStream fis = new FileInputStream(
                "src/main/resources/config.properties");
             InputStreamReader ir = new InputStreamReader(fis, "UTF-8")) {
            // 创建Properties集合对象
            Properties properties = new Properties();
            // 加载文件中的数据
            properties.load(ir);
            // 输出校验
            System.out.println(properties);
        } catch (IOException e) {
            System.out.println("出现·异常：" + e.getMessage());
        }
    }

    private static void test() {
         /*
            HashSet集合底层使用的是HashMap进行存储数据，
            本质上所有的Set集合存储数据都是将数据存储到Map集合的key列中，
            通过Map集合key不可重复的特性，实现Set集合中存储的元素不可重复；
         */
        HashSet<Integer> integers = new HashSet<>();
        // HashSet是利用HashMap存储元素，所以在add()方法内，
        // 使用了HashMap中的put方法，进行存储元素
        integers.add(1);
        // 移除元素使用remove()方法
        integers.remove(1);
        System.out.println(integers);
    }
}
